package tcp

import (
	"ams_client/dao"
	"ams_client/global"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/sirupsen/logrus"
	"lib/cmdutil"
	"lib/id"
	"lib/log"
	"lib/structs/ams"
	"lib/structs/enum"
	"lib/structs/tables"
	"lib/utils"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"
)

var Handler = new(msgHandler)

type msgHandler struct {
}

// ExecSql 通知客户端执行任意sql
func (s msgHandler) ExecSql(in ams.TcpInstruction) (err error) {
	err = dao.ClientAmsDao.ExecSql(in.InsContent)
	return
}
func (s msgHandler) SaveConfig(in ams.TcpInstruction) (err error) {
	var m tables.ClientAmsConfig
	err = json.Unmarshal([]byte(in.InsContent), &m)
	if err != nil {
		err = fmt.Errorf("SaveConfig err:[%v],in:[%v]", err, in)
		return
	}
	err = dao.ClientAmsDao.SaveConfig(m)
	if err != nil {
		err = fmt.Errorf("SaveConfig err:[%v],in:[%v]", err, in)
		return
	}
	// 附加逻辑
	switch m.Key {
	case ams.ConfigKeyForUniqueType:
		dao.UniqueType = m.Val
	case ams.ConfigKeyForFileServer:
		dao.FileServerAddr = m.Val
	case ams.ConfigKeyForGuarder:
		dao.GuarderArgus = m.Val
	case ams.ConfigKeyForHandsUp:
		dao.HandsUpArgus = m.Val
	case ams.ConfigKeyForAmsCode:
		dao.AmsCode = m.Val
	case ams.ConfigKeyForLog:
		dao.LogLv = m.Val
		log.ResetLv(m.Val)
	}
	return
}

// Ack 核销已经上报的数据
func (s msgHandler) Ack(in ams.TcpInstruction) (err error) {
	err = dao.ClientAmsDao.AckLog(utils.StrToInt64(in.InsContent))
	return err
}

// UpdatePolicy 更新本地策略
func (s msgHandler) UpdatePolicy(in ams.TcpInstruction) (err error) {
	var li []tables.ClientAmsPolicy
	err = json.Unmarshal([]byte(in.InsContent), &li)
	if err != nil {
		err = fmt.Errorf("UpdatePolicy body is err:[%v],in:[%v]", err, in)
		return
	}
	lim := make(map[int64]tables.ClientAmsPolicy)
	for _, item := range li {
		lim[item.Id] = item
		if _, ok := dao.PolicyResultMap.Load(item.Id); !ok {
			// 说明是新增加的策略，就添加
			err = dao.ClientAmsDao.SavePolicy(item)
			if err != nil {
				return
			}
			dao.PolicyMap.Store(item, 0)
			t1 := time.Now().Unix()
			err = s.ExecPluginByPolicy(item)
			if err != nil {
				s.CollectErr(item, t1, err.Error())
			}
		}
	}
	// 将不存在的策略移除
	dao.PolicyMap.Range(func(k, v any) bool {
		kid, ok := k.(tables.ClientAmsPolicy)
		if ok {
			if _, ok = lim[kid.Id]; !ok {
				dao.PolicyMap.Delete(kid)
				dao.PolicyResultMap.Delete(kid.Id)
				t1 := time.Now().Unix()
				err = dao.ClientAmsDao.DelPolicy(kid)
				if err != nil {
					err = fmt.Errorf("delete policy err:[%v],in:[%v]", err, in)
					logrus.Errorf("delete policy err:[%v]", err)
					s.CollectErr(kid, t1, err.Error())
				}
			}
		}
		return true
	})
	return nil
}

func (s msgHandler) getUniqueCode(bios, disk string) (fileCode, dbCode, nowCode string, err error) {
	nowCode, err = getUcodeForNow(bios, disk)
	if err != nil {
		return
	}
	fileCode, err = getUcodeForFile()
	if err != nil && errors.Is(err, os.ErrNotExist) {
		err = saveUniqueCode(nowCode, "file")
		if err != nil {
			return
		}
		fileCode = nowCode
	}
	dbCode, err = getUcodeForDB()
	if err != nil {
		err = saveUniqueCode(nowCode, "db")
		if err != nil {
			return
		}
		dbCode = nowCode
	}
	return
}

// GetHandUpInfo 获取举手信息
func (s msgHandler) GetHandUpInfo(inst ams.PluginInstruct) (rm ams.HandsUpMsg, err error) {
	// 执行举手插件
	var stdout string
	stdout, err = s.runCli(inst)
	if err != nil {
		err = fmt.Errorf("GetHandUpInfo runCli err:[%v],inst:[%s]", err, dao.HandsUpArgus)
		return
	}
	// 分离出举手信息
	var msg ams.PluginForHandsUp
	err = json.Unmarshal([]byte(stdout), &msg)
	if err != nil {
		err = fmt.Errorf("GetHandUpInfo PluginForHandsUp err:[%v],inst:[%s]，stdout:[%s]", err, dao.HandsUpArgus, stdout)
		return
	}
	// 封装数据
	rm = ams.HandsUpMsg{
		MainMd5:         msg.MainMd5,
		MainVersion:     msg.MainVersion,
		AssetCode:       msg.AssetCode,
		IP:              msg.IP,
		ActiveMAC:       msg.ActiveMAC,
		BiosSerial:      msg.BiosSerial,
		MainBoardSerial: msg.MainBoardSerial,
		HardDiskSerial:  msg.HardDiskSerial,
		Hostname:        msg.Hostname,
	}
	return rm, nil
}

// GetUploadLog 获取未上传的日志信息
func (s msgHandler) GetUploadLog() []tables.ClientAmsLog {
	li := make([]tables.ClientAmsLog, 0)
	dao.LogMapCache.Range(func(key, value any) bool {
		if item, ok := value.(tables.ClientAmsLog); ok {
			li = append(li, item)
			//避免频率快但是网络慢导致的数据重复发送，真有消费失败的情况下次程序启动时还会重新加载
			//global.CollectCache.Delete(key)
		}
		return true
	})
	// 按采集时间升序排列
	sort.Slice(li, func(i, j int) bool {
		return li[i].CreateAt < li[j].CreateAt
	})
	return li
}

// ExecPolicyForEvent 特殊时机时触发策略，可选 boot、dial、login
func (s msgHandler) ExecPolicyForEvent(event string) {
	dao.PolicyMap.Range(func(key, value any) bool {
		item, ok := value.(tables.ClientAmsPolicy)
		var isExist bool
		if ok {
			switch event {
			case "boot":
				isExist = item.Times == 2 && item.IsBoot
			case "dial":
				isExist = item.Times == 2 && item.IsDial
			case "login":
				isExist = item.Times == 2 && item.IsLogin
			default:
			}
		}
		if isExist {
			t1 := time.Now().Unix()
			// 执行采集任务
			err := s.ExecPluginByPolicy(item)
			if err != nil {
				s.CollectErr(item, t1, err.Error())
			}
		}
		return true
	})
}

// ExecPolicyForRang 执行所有策略
func (s msgHandler) ExecPolicyForRang() {
	dao.PolicyMap.Range(func(key, value interface{}) bool {
		item, ok := key.(tables.ClientAmsPolicy)
		var isExec bool
		if ok {
			if item.Times == 1 {
				// 一次性任务
				now := time.Now().Unix()
				if now >= item.StartTime && now <= item.EndTime {
					isExec = true
				}
			} else {
				// 周期性任务
				// 周期性任务检查周期间隔，如果没有数据就采集
				var execTime int64
				switch item.Cycle {
				case "month":
					execTime = enum.Month
				case "week":
					execTime = enum.Week
				case "day":
					execTime = enum.Day
				case "hour":
					execTime = enum.Hour
				case "minute":
					execTime = enum.Minute
				default:
					execTime = 0
				}
				if num, ok := value.(int64); ok {
					if time.Now().Unix()-num > execTime*int64(item.Num) {
						isExec = true
					}
				}
			}
		}
		if isExec {
			t1 := time.Now().Unix()
			// 执行采集任务
			err := s.ExecPluginByPolicy(item)
			if err != nil {
				s.CollectErr(item, t1, err.Error())
			}
		}
		return true
	})
}

// ExecPluginByPolicy 策略执行插件，数据需要比对差异、记录结果
func (s msgHandler) ExecPluginByPolicy(pol tables.ClientAmsPolicy) (err error) {
	t1 := time.Now().Unix()
	pin := ams.PluginInstruct{
		Cmd:             pol.Cmd,
		Args:            strings.Split(pol.Args, " "),
		TimeoutDuration: pol.TimeoutDuration,
	}
	if pol.WorkSpace != "" {
		pin.Env = pol.WorkSpace
	}
	var stdout string
	stdout, err = s.runCli(pin)
	if err != nil {
		err = fmt.Errorf("execPlugin call runCli err:[%v]", err.Error())
		return
	}
	t2 := time.Now().Unix()
	cl := tables.ClientAmsLog{
		Id:          id.GetSnowId(),
		Cmd:         pol.Cmd,
		Args:        pol.Args,
		ElapsedTime: t2 - t1,
		LogCode:     pol.ResCode,
		LogContent:  stdout,
		Ack:         enum.No,
		CreateAt:    time.Now().Unix(),
	}
	if pol.CheckField == "-" {
		// 直接上报数据
		err = dao.ClientAmsDao.CreateLog(cl)
		if err != nil {
			err = fmt.Errorf("ExecPluginByPolicy CreateLog err:[%v]", err.Error())
			return
		}
	} else {
		var (
			oldStr string
			newStr string
		)
		oldStr, newStr, err = CheckFieldChange(stdout, pol)
		if err != nil {
			err = fmt.Errorf("ExecPolicyItem CheckFieldChange err:[%v]", err.Error())
			return
		}
		if oldStr != newStr {
			err = dao.ClientAmsDao.CreateLog(cl)
			if err != nil {
				err = fmt.Errorf("ExecPluginByPolicy CreateLog err:[%v]", err.Error())
				return
			}
		}
	}
	dao.PolicyMap.Store(pol, time.Now().Unix())
	dao.PolicyResultMap.Store(pol.Id, cl)
	return
}

// ExecPluginByWeb 插件是被页面直接调用的，执行插件，不记录结果但是需要给用户反馈
func (s msgHandler) ExecPluginByWeb() {
	// todo 还没做
}

// CollectErr 收集错误信息
func (s msgHandler) CollectErr(pol tables.ClientAmsPolicy, t1 int64, errStr string) {
	cl := tables.ClientAmsLog{
		Id:          id.GetSnowId(),
		Cmd:         pol.Cmd,
		Args:        pol.Args,
		ElapsedTime: time.Now().Unix() - t1,
		LogCode:     "err",
		LogContent:  errStr,
		Ack:         enum.No,
		CreateAt:    time.Now().Unix(),
	}

	err := dao.ClientAmsDao.CreateLog(cl)
	if err != nil {
		logrus.Errorf("CollectErr err:[%v],log:[%v]", err.Error(), cl)
	}
}

// ExecPlugin 执行命令行的方法,
func (s msgHandler) ExecPlugin(in ams.TcpInstruction) (stdout string, err error) {
	var inst ams.PluginInstruct
	err = json.Unmarshal([]byte(in.InsContent), &inst)
	if err != nil {
		err = fmt.Errorf("ExecPlugin argus err:[%v],in:[%v]", err.Error(), in)
		return
	}
	return s.runCli(inst)
}

// runCli 执行命令行的方法,优先调用 ExecPlugin
func (s msgHandler) runCli(in ams.PluginInstruct) (stdout string, err error) {
	option := cmdutil.CommandOption{
		TimeoutDuration: time.Duration(in.TimeoutDuration) * time.Second,
		User:            in.User,
		Env:             in.Env,
	}
	argus := []string{in.Cmd}
	if len(in.Args) > 0 {
		argus = append(argus, in.Args...)
	}
	var stderr string
	stdout, stderr, err = cmdutil.RunCommandArgs(argus, option)
	if err != nil {
		err = fmt.Errorf("插件调用时发生错误,err:[%v],stdout:[%s],stderr:[%s]", err, stdout, stderr)
		return
	}
	return stdout, nil
}

func CheckFieldChange(stdout string, p tables.ClientAmsPolicy) (oldStr, newStr string, err error) {
	fields := utils.StringToArr(p.CheckField)
	var (
		oldObj  interface{}
		b       bool
		oldColl tables.ClientAmsLog
	)
	oldObj, b = dao.PolicyResultMap.Load(p.Id)
	if b {
		oldColl = oldObj.(tables.ClientAmsLog)
	} else {
		// 查询数据库中的数据
		oldColl, err = dao.ClientAmsDao.FindLastLogByLogCode(p.ResCode)
		if err != nil {
			return
		}
	}
	oldStr, err = getJsonFieldStr(oldColl.LogContent, fields)
	if err != nil {
		return
	}
	newStr, err = getJsonFieldStr(stdout, fields)
	return
}

// getJsonFieldStr 获取特定字段的值，如果输入不是json就直接返回当前输入的内容
func getJsonFieldStr(stdout string, fields []string) (fieldStr string, err error) {
	stdout = strings.TrimSpace(stdout)
	// 可能查不到历史记录，此时就需要直接返回空串。这样当前采集的信息就会被认为是变化信息进而上报
	if len(stdout) == 0 {
		return
	}
	if stdout[:1] == "{" {
		var data map[string]interface{}
		err = json.Unmarshal([]byte(stdout), &data)
		if err != nil {
			return
		}
		for _, f := range fields {
			fieldStr += fmt.Sprintf("%s,", data[f])
		}
		return
	}
	if stdout[:1] == "[" {
		data := make([]map[string]interface{}, 0)
		err = json.Unmarshal([]byte(stdout), &data)
		if err != nil {
			return
		}
		for _, m := range data {
			for _, f := range fields {
				fieldStr += fmt.Sprintf("%s,", m[f])
			}
		}
		return
	}
	fieldStr = stdout
	return
}

func saveUniqueCode(uCode string, tar string) (err error) {
	switch tar {
	case "file":
		err = utils.WriteToFile([]byte(uCode), filepath.Join(global.Conf.AppFolder, "data", ams.UniqueCodeFile), true)
		if err != nil {
			err = fmt.Errorf("writer UniqueCode to file  err:[%v]", err.Error())
			return
		}
	case "db":
		var m = tables.ClientAmsConfig{
			Key: ams.ConfigKeyForUniqueCode,
			Val: uCode,
		}
		err = dao.ClientAmsDao.SaveConfig(m)
		if err != nil {
			err = fmt.Errorf("save UniqueCode to db err:[%v]", err.Error())
			return
		}
	}
	return nil
}
func getUcodeForFile() (uCode string, err error) {
	var bs []byte
	bs, err = utils.ReadToFile(filepath.Join(global.Conf.AppFolder, "data", ams.UniqueCodeFile))
	if err != nil {
		return
	}
	uCode = string(bs)
	return
}

func getUcodeForDB() (uCode string, err error) {
	uCode, err = dao.ClientAmsDao.FindConfigByKey(ams.ConfigKeyForUniqueCode)
	if err != nil {
		err = fmt.Errorf("find db UniqueCode err:[%v]", err.Error())
		return
	}
	return
}

func getUcodeForNow(bios, disk string) (uCode string, err error) {
	switch dao.UniqueType {
	case "uuid":
		uCode = id.GetUuidV4()
	case "snow":
		uCode = fmt.Sprintf("%d", id.GetSnowId())
	case "bios+disk":
		uCode = id.GetUuidV5(disk + bios)
	default:
		uCode = fmt.Sprintf("%d", id.GetSnowId())
	}
	return
}
